/*
 *	 PVRLoader
 *   Author: pierre lepers
 *   Date: 17/09/2014 11:09
 *
 *	 PVR v2 (legacy) parser
 *   TODO : Add Support for PVR v3 format
 *   TODO : implement loadMipmaps option
 */

THREE.PVRLoader = function ( manager ) {

	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

	this._parser = THREE.PVRLoader.parse;

};

THREE.PVRLoader.prototype = Object.create( THREE.CompressedTextureLoader.prototype );
THREE.PVRLoader.prototype.constructor = THREE.PVRLoader;


THREE.PVRLoader.parse = function ( buffer, loadMipmaps ) {

	var headerLengthInt = 13;
	var header = new Uint32Array( buffer, 0, headerLengthInt );

	var pvrDatas = {
		buffer: buffer,
		header : header,
		loadMipmaps : loadMipmaps
	};

	// PVR v3
	if ( header[ 0 ] === 0x03525650 ) {

		return THREE.PVRLoader._parseV3( pvrDatas );

	}
	// PVR v2
	else if ( header[ 11 ] === 0x21525650 ) {

		return THREE.PVRLoader._parseV2( pvrDatas );

	} else {

		throw new Error( "[THREE.PVRLoader] Unknown PVR format" );

	}

};

THREE.PVRLoader._parseV3 = function ( pvrDatas ) {

	var header = pvrDatas.header;
	var bpp, format;


	var metaLen 	  = header[ 12 ],
		pixelFormat   =  header[ 2 ],
		height        =  header[ 6 ],
		width         =  header[ 7 ],
		numSurfs      =  header[ 9 ],
		numFaces      =  header[ 10 ],
		numMipmaps    =  header[ 11 ];

	switch ( pixelFormat ) {
		case 0 : // PVRTC 2bpp RGB
			bpp = 2;
			format = THREE.RGB_PVRTC_2BPPV1_Format;
			break;
		case 1 : // PVRTC 2bpp RGBA
			bpp = 2;
			format = THREE.RGBA_PVRTC_2BPPV1_Format;
			break;
		case 2 : // PVRTC 4bpp RGB
			bpp = 4;
			format = THREE.RGB_PVRTC_4BPPV1_Format;
			break;
		case 3 : // PVRTC 4bpp RGBA
			bpp = 4;
			format = THREE.RGBA_PVRTC_4BPPV1_Format;
			break;
		default :
			throw new Error( "pvrtc - unsupported PVR format " + pixelFormat );
	}

	pvrDatas.dataPtr 	 = 52 + metaLen;
	pvrDatas.bpp 		 = bpp;
	pvrDatas.format 	 = format;
	pvrDatas.width 		 = width;
	pvrDatas.height 	 = height;
	pvrDatas.numSurfaces = numFaces;
	pvrDatas.numMipmaps  = numMipmaps;

	pvrDatas.isCubemap 	= ( numFaces === 6 );

	return THREE.PVRLoader._extract( pvrDatas );

};

THREE.PVRLoader._parseV2 = function ( pvrDatas ) {

	var header = pvrDatas.header;

	var headerLength  =  header[ 0 ],
		height        =  header[ 1 ],
		width         =  header[ 2 ],
		numMipmaps    =  header[ 3 ],
		flags         =  header[ 4 ],
		dataLength    =  header[ 5 ],
		// bpp           =  header[ 6 ],
		bitmaskRed    =  header[ 7 ],
		bitmaskGreen  =  header[ 8 ],
		bitmaskBlue   =  header[ 9 ],
		bitmaskAlpha  =  header[ 10 ],
		pvrTag        =  header[ 11 ],
		numSurfs      =  header[ 12 ];


	var TYPE_MASK = 0xff;
	var PVRTC_2 = 24,
		PVRTC_4 = 25;

	var formatFlags = flags & TYPE_MASK;



	var bpp, format;
	var _hasAlpha = bitmaskAlpha > 0;

	if ( formatFlags === PVRTC_4 ) {

		format = _hasAlpha ? THREE.RGBA_PVRTC_4BPPV1_Format : THREE.RGB_PVRTC_4BPPV1_Format;
		bpp = 4;

	} else if ( formatFlags === PVRTC_2 ) {

		format = _hasAlpha ? THREE.RGBA_PVRTC_2BPPV1_Format : THREE.RGB_PVRTC_2BPPV1_Format;
		bpp = 2;

	} else
		throw new Error( "pvrtc - unknown format " + formatFlags );



	pvrDatas.dataPtr 	 = headerLength;
	pvrDatas.bpp 		 = bpp;
	pvrDatas.format 	 = format;
	pvrDatas.width 		 = width;
	pvrDatas.height 	 = height;
	pvrDatas.numSurfaces = numSurfs;
	pvrDatas.numMipmaps  = numMipmaps + 1;

	// guess cubemap type seems tricky in v2
	// it juste a pvr containing 6 surface (no explicit cubemap type)
	pvrDatas.isCubemap 	= ( numSurfs === 6 );

	return THREE.PVRLoader._extract( pvrDatas );

};


THREE.PVRLoader._extract = function ( pvrDatas ) {

	var pvr = {
		mipmaps: [],
		width: pvrDatas.width,
		height: pvrDatas.height,
		format: pvrDatas.format,
		mipmapCount: pvrDatas.numMipmaps,
		isCubemap : pvrDatas.isCubemap
	};

	var buffer = pvrDatas.buffer;



	// console.log( "--------------------------" );

	// console.log( "headerLength ", headerLength);
	// console.log( "height       ", height      );
	// console.log( "width        ", width       );
	// console.log( "numMipmaps   ", numMipmaps  );
	// console.log( "flags        ", flags       );
	// console.log( "dataLength   ", dataLength  );
	// console.log( "bpp          ", bpp         );
	// console.log( "bitmaskRed   ", bitmaskRed  );
	// console.log( "bitmaskGreen ", bitmaskGreen);
	// console.log( "bitmaskBlue  ", bitmaskBlue );
	// console.log( "bitmaskAlpha ", bitmaskAlpha);
	// console.log( "pvrTag       ", pvrTag      );
	// console.log( "numSurfs     ", numSurfs    );




	var dataOffset = pvrDatas.dataPtr,
		bpp = pvrDatas.bpp,
		numSurfs = pvrDatas.numSurfaces,
		dataSize = 0,
		blockSize = 0,
		blockWidth = 0,
		blockHeight = 0,
		widthBlocks = 0,
		heightBlocks = 0;



	if ( bpp === 2 ) {

		blockWidth = 8;
		blockHeight = 4;

	} else {

		blockWidth = 4;
		blockHeight = 4;

	}

	blockSize = ( blockWidth * blockHeight ) * bpp / 8;

	pvr.mipmaps.length = pvrDatas.numMipmaps * numSurfs;

	var mipLevel = 0;

	while ( mipLevel < pvrDatas.numMipmaps ) {

		var sWidth = pvrDatas.width >> mipLevel,
		sHeight = pvrDatas.height >> mipLevel;

		widthBlocks = sWidth / blockWidth;
		heightBlocks = sHeight / blockHeight;

		// Clamp to minimum number of blocks
		if ( widthBlocks < 2 )
			widthBlocks = 2;
		if ( heightBlocks < 2 )
			heightBlocks = 2;

		dataSize = widthBlocks * heightBlocks * blockSize;


		for ( var surfIndex = 0; surfIndex < numSurfs; surfIndex ++ ) {

			var byteArray = new Uint8Array( buffer, dataOffset, dataSize );

			var mipmap = {
				data: byteArray,
				width: sWidth,
				height: sHeight
			};

			pvr.mipmaps[ surfIndex * pvrDatas.numMipmaps + mipLevel ] = mipmap;

			dataOffset += dataSize;


		}

		mipLevel ++;

	}


	return pvr;

};
